package com.tju.resource.server.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.net.Ipv4Util;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.hutool.system.oshi.CpuInfo;
import cn.hutool.system.oshi.OshiUtil;
import com.tju.resource.server.SerInfoService;
import com.tju.resource.tool.tool.IpUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.GlobalMemory;
import oshi.hardware.NetworkIF;
import oshi.software.os.FileSystem;
import oshi.software.os.OSFileStore;
import oshi.software.os.OperatingSystem;
import oshi.util.FormatUtil;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.tju.resource.server.SerInfoService.KEY_ENUM.*;

@Service
public class SerInfoServiceImpl implements SerInfoService {
    @Override
    public JSONObject all() {
        JSONObject serInfo = JSONUtil.parseObj("{}");
        JSONObject cpu = cpu();
        JSONObject memory = memory();
        JSONObject jvm = jvm();
        JSONObject jdk = jdk();
        JSONObject about = about();
        JSONObject pro = pro();
        JSONObject disk = disk();

        serInfo.putAll(cpu);
        serInfo.putAll(memory);
        serInfo.putAll(jvm);
        serInfo.putAll(jdk);
        serInfo.putAll(about);
        serInfo.putAll(pro);
        serInfo.putAll(disk);

        return serInfo;
    }

    @Override
    public JSONObject cpu() {
        JSONObject ret = JSONUtil.parseObj("{}");
        CpuInfo easyCpu = OshiUtil.getCpuInfo();
        CentralProcessor processor = OshiUtil.getProcessor();

        ret.putOnce(CPU_MODEL, processor.getName());
        ret.putOnce(CPU_CORES, processor.getPhysicalProcessorCount());
        ret.putOnce(CPU_PROCESS, easyCpu.getCpuNum());
        ret.putOnce(CPU_USER_USAGE, easyCpu.getUsed() + "%");
        ret.putOnce(CPU_SYS_USAGE, easyCpu.getSys() + "%");
        ret.putOnce(CPU_FREE_USAGE, easyCpu.getFree() + "%");

        return ret;
    }

    @Override
    public JSONObject memory() {
        JSONObject ret = JSONUtil.parseObj("{}");
        SystemInfo systemInfo = new SystemInfo();
        GlobalMemory memory = systemInfo.getHardware().getMemory();
        long totalByte = memory.getTotal();
        long memoryAvailable = memory.getAvailable();

        ret.putOnce(MEMORY_TOTAL, formatByte(totalByte));
        ret.putOnce(MEMORY_USAGE, formatByte(totalByte - memoryAvailable));
        ret.putOnce(MEMORY_FREE, formatByte(memoryAvailable));
        ret.putOnce(MEMORY_USAGE_CENT, new DecimalFormat("#.##%").format((totalByte - memoryAvailable) * 1.0 / totalByte));

        return ret;
    }

    @Override
    public JSONObject jvm() {
        JSONObject ret = JSONUtil.parseObj("{}");
        double jvmTotal = div(Runtime.getRuntime().totalMemory(), 1024 * 1024, 100);
        double jvmFree = div(Runtime.getRuntime().freeMemory(), 1024 * 1024, 100);
        double jvmUsage = jvmTotal - jvmFree;
        double jvmUsageCent = (jvmUsage / jvmTotal) * 100.0;
        long time = ManagementFactory.getRuntimeMXBean().getStartTime();
        long runTime = ManagementFactory.getRuntimeMXBean().getStartTime();
        LocalDateTime dateTime = getDateTime(runTime);
        LocalDateTime now = LocalDateTime.now();
        Duration duration = Duration.between(dateTime, now);
        String jvmName = ManagementFactory.getRuntimeMXBean().getVmName();

        ret.putOnce(JVM_MEMORY_TOTAL, new BigDecimal(jvmTotal).setScale(2, RoundingMode.HALF_EVEN).doubleValue() + "MB");
        ret.putOnce(JVM_MEMORY_FREE, new BigDecimal(jvmFree).setScale(2, RoundingMode.HALF_EVEN).doubleValue() + "MB");
        ret.putOnce(JVM_MEMORY_USAGE, new BigDecimal(jvmUsage).setScale(2, RoundingMode.HALF_EVEN).doubleValue() + "MB");
        ret.putOnce(JVM_MEMORY_USAGE_CENT, String.format("%.2f", jvmUsageCent) + "%");
        ret.putOnce(JVM_START_TIME, DateUtil.format(new Date(time), "yyyy-MM-dd HH:mm:ss"));
        ret.putOnce(JVM_RUN_TIME, duration.toDays() + "天" + duration.toHours() + "小时" + (duration.toMinutes() - (duration.toHours() * 60)) + "分钟");
        ret.putOnce(JVM_NAME, jvmName);

        return ret;
    }

    @Override
    public JSONObject jdk() {
        JSONObject ret = JSONUtil.parseObj("{}");
        Properties props = System.getProperties();
        String jdkVer = props.getProperty("java.version");
        String jdkHome = props.getProperty("java.home");

        ret.putOnce(JDK_VERSION, jdkVer);
        ret.putOnce(JDK_PATH, jdkHome);

        return ret;
    }

    @Override
    public JSONObject about() {
        JSONObject ret = JSONUtil.parseObj("{}");
        Properties props = System.getProperties();
        OperatingSystem os = OshiUtil.getOs();
        String compName = os.getNetworkParams().getHostName();
        String osName = props.getProperty("os.name");
        String hostIp = IpUtil.getIpv4OnNet();

        ret.putOnce(SERVER_COMP_NAME, compName);
        ret.putOnce(SERVER_OS_MODEL, osName);
        ret.putOnce(SERVER_OS_ARCH, props.getProperty("os.arch"));
        ret.putOnce(SERVER_LOCAL_IP, hostIp);

        return ret;
    }

    @Override
    public JSONObject pro() {
        JSONObject ret = JSONUtil.parseObj("{}");
        String dir = System.getProperty("user.dir");

        ret.putOnce(WORK_DIR, dir);

        return ret;
    }

    @Override
    public JSONObject disk() {
        JSONObject ret = JSONUtil.parseObj("{}");
        JSONArray diskList = JSONUtil.parseArray("[]");
        FileSystem fileSystem = OshiUtil.getOs().getFileSystem();
        OSFileStore[] fsArray = fileSystem.getFileStores();

        for (OSFileStore fs : fsArray) {
            long free = fs.getUsableSpace();
            long total = fs.getTotalSpace();
            long used = total - free;
            JSONObject diskInfo = JSONUtil.parseObj("{}");

            diskInfo.putOnce(DISK_PATH, fs.getMount());
            diskInfo.putOnce(DISK_TYPE, fs.getType());
            diskInfo.putOnce(DISK_NAME, fs.getName());
            diskInfo.putOnce(DISK_SIZE_TOTAL, FormatUtil.formatBytes(total));
            diskInfo.putOnce(DISK_SIZE_FREE, FormatUtil.formatBytes(free));
            diskInfo.putOnce(DISK_SIZE_USAGE, FormatUtil.formatBytes(used));
            diskInfo.putOnce(DISK_SIZE_USAGE_CENT, Double.parseDouble(String.format("%.2f", (double)used * 100 / total)) + "%");

            diskList.add(diskInfo);
        }
        ret.putOnce(DISK_LIST, diskList);

        return ret;
    }

    /**
     * 提供（相对）精确的除法运算。当发生除不尽的情况时，由scale参数指
     * 定精度，以后的数字四舍五入。
     *
     * @param v1    被除数
     * @param v2    除数
     * @param scale 表示表示需要精确到小数点以后几位。
     * @return 两个参数的商
     */
    private static double div(double v1, double v2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException(
                    "The scale must be a positive integer or zero");
        }
        BigDecimal b1 = new BigDecimal(Double.toString(v1));
        BigDecimal b2 = new BigDecimal(Double.toString(v2));
        if (b1.compareTo(BigDecimal.ZERO) == 0) {
            return BigDecimal.ZERO.doubleValue();
        }
        return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue();
    }

    private static String formatByte(long byteNumber) {
        //换算单位
        double FORMAT = 1024.0;
        double kbNumber = byteNumber / FORMAT;
        if (kbNumber < FORMAT) {
            return new DecimalFormat("#.##KB").format(kbNumber);
        }
        double mbNumber = kbNumber / FORMAT;
        if (mbNumber < FORMAT) {
            return new DecimalFormat("#.##MB").format(mbNumber);
        }
        double gbNumber = mbNumber / FORMAT;
        if (gbNumber < FORMAT) {
            return new DecimalFormat("#.##GB").format(gbNumber);
        }
        double tbNumber = gbNumber / FORMAT;
        return new DecimalFormat("#.##TB").format(tbNumber);
    }

    private LocalDateTime getDateTime(Long time){
        return Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault().getRules().getOffset(LocalDateTime.now())).toLocalDateTime();
    }
}
